#include "pch.h"
#include "SenzibleGame.h"
#include "GameClock.h"
#include <iostream>
#include <string>
#include <memory>

using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Windows::UI::Core;
using namespace Windows::Foundation;
using namespace Windows::Graphics::Display;

SenzibleGame::SenzibleGame()
{
}

void SenzibleGame::Start(GameState* initialGameState)
{
	Window = CoreWindow::GetForCurrentThread();
	/*CurrentGameState = initialGameState;
	CurrentGameState->Initialize();
	CurrentGameState->LoadContent();*/

	CreateDeviceResources();
	CreateWindowSizeDependentResources();

	GameClock gameClock;

	while(!IsClosed)
	{
		if(IsVisible)
		{
			gameClock.Update();
			CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

			D3dContext->OMSetRenderTargets(
				1,
				RenderTargetView.GetAddressOf(),
				nullptr
				);

			const float clearColor[4] = { 0.071f, 0.404f, 0.061f, 1.0f };
			D3dContext->ClearRenderTargetView(
				RenderTargetView.Get(),
				clearColor
				);

			Present();

			/*CurrentGameState->Update(gameClock);
			CurrentGameState->Draw();*/
		}
		else
		{
			CoreWindow::GetForCurrentThread()->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessOneAndAllPending);
		}
	}
}

void SenzibleGame::CreateDeviceResources()
{
	// This flag adds support for surfaces with a different color channel ordering
	// than the API default. It is required for compatibility with Direct2D.
	UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
	// If the project is in a debug build, enable debugging via SDK Layers with this flag.
	creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

	// This array defines the set of DirectX hardware feature levels this app will support.
	// Note the ordering should be preserved.
	// Don't forget to declare your application's minimum required feature level in its
	// description.  All applications are assumed to support 9.1 unless otherwise stated.
	D3D_FEATURE_LEVEL featureLevels[] = 
	{
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
		D3D_FEATURE_LEVEL_10_1,
		D3D_FEATURE_LEVEL_10_0,
		D3D_FEATURE_LEVEL_9_3,
		D3D_FEATURE_LEVEL_9_2,
		D3D_FEATURE_LEVEL_9_1
	};

	// Create the Direct3D 11 API device object and a corresponding context.
	ComPtr<ID3D11Device> device;
	ComPtr<ID3D11DeviceContext> context;
	DX::Try(
		D3D11CreateDevice(
		nullptr, // Specify nullptr to use the default adapter.
		D3D_DRIVER_TYPE_HARDWARE,
		nullptr,
		creationFlags, // Set set debug and Direct2D compatibility flags.
		featureLevels, // List of feature levels this app can support.
		ARRAYSIZE(featureLevels),
		D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps.
		&device, // Returns the Direct3D device created.
		&FeatureLevel, // Returns feature level of device created.
		&context // Returns the device immediate context.
		)
		);

	// Get the Direct3D 11.1 API device and context interfaces.
	DX::Try(
		device.As(&D3dDevice)
		);

	DX::Try(
		context.As(&D3dContext)
		);
}

void SenzibleGame::CreateWindowSizeDependentResources()
{
	// Store the window bounds so the next time we get a SizeChanged event we can
	// avoid rebuilding everything if the size is identical.
	WindowBounds = Window->Bounds;

	// Calculate the necessary swap chain and render target size in pixels.
	float windowWidth = ConvertDipsToPixels(WindowBounds.Width);
	float windowHeight = ConvertDipsToPixels(WindowBounds.Height);

	// The width and height of the swap chain must be based on the window's
	// landscape-oriented width and height. If the window is in a portrait
	// orientation, the dimensions must be reversed.
	Orientation = DisplayProperties::CurrentOrientation;
	bool swapDimensions =
		Orientation == DisplayOrientations::Portrait ||
		Orientation == DisplayOrientations::PortraitFlipped;
	RenderTargetSize.Width = swapDimensions ? windowHeight : windowWidth;
	RenderTargetSize.Height = swapDimensions ? windowWidth : windowHeight;

	if(SwapChain != nullptr)
	{
		// If the swap chain already exists, resize it.
		DX::Try(
			SwapChain->ResizeBuffers(
			2, // Double-buffered swap chain.
			static_cast<UINT>(RenderTargetSize.Width),
			static_cast<UINT>(RenderTargetSize.Height),
			DXGI_FORMAT_B8G8R8A8_UNORM,
			0
			)
			);
	}
	else
	{
		// Otherwise, create a new one using the same adapter as the existing Direct3D device.
		DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};
		swapChainDesc.Width = static_cast<UINT>(RenderTargetSize.Width); // Match the size of the window.
		swapChainDesc.Height = static_cast<UINT>(RenderTargetSize.Height);
		swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
		swapChainDesc.Stereo = false;
		swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
		swapChainDesc.SampleDesc.Quality = 0;
		swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
		swapChainDesc.Scaling = DXGI_SCALING_NONE;
		swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
		swapChainDesc.Flags = 0;

		ComPtr<IDXGIDevice1>  dxgiDevice;
		DX::Try(
			D3dDevice.As(&dxgiDevice)
			);

		ComPtr<IDXGIAdapter> dxgiAdapter;
		DX::Try(
			dxgiDevice->GetAdapter(&dxgiAdapter)
			);

		ComPtr<IDXGIFactory2> dxgiFactory;
		DX::Try(
			dxgiAdapter->GetParent(
			__uuidof(IDXGIFactory2), 
			&dxgiFactory
			)
			);

		Windows::UI::Core::CoreWindow^ window = Window.Get();
		DX::Try(
			dxgiFactory->CreateSwapChainForCoreWindow(
			D3dDevice.Get(),
			reinterpret_cast<IUnknown*>(window),
			&swapChainDesc,
			nullptr, // Allow on all displays.
			&SwapChain
			)
			);

		// Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and
		// ensures that the application will only render after each VSync, minimizing power consumption.
		DX::Try(
			dxgiDevice->SetMaximumFrameLatency(1)
			);
	}

	// Set the proper orientation for the swap chain, and generate the
	// 3D matrix transformation for rendering to the rotated swap chain.
	DXGI_MODE_ROTATION rotation = DXGI_MODE_ROTATION_UNSPECIFIED;
	switch (Orientation)
	{
	case DisplayOrientations::Landscape:
		rotation = DXGI_MODE_ROTATION_IDENTITY;
		OrientationTransform3D = XMFLOAT4X4( // 0-degree Z-rotation
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
		break;

	case DisplayOrientations::Portrait:
		rotation = DXGI_MODE_ROTATION_ROTATE270;
		OrientationTransform3D = XMFLOAT4X4( // 90-degree Z-rotation
			0.0f, 1.0f, 0.0f, 0.0f,
			-1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
		break;

	case DisplayOrientations::LandscapeFlipped:
		rotation = DXGI_MODE_ROTATION_ROTATE180;
		OrientationTransform3D = XMFLOAT4X4( // 180-degree Z-rotation
			-1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, -1.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
		break;

	case DisplayOrientations::PortraitFlipped:
		rotation = DXGI_MODE_ROTATION_ROTATE90;
		OrientationTransform3D = XMFLOAT4X4( // 270-degree Z-rotation
			0.0f, -1.0f, 0.0f, 0.0f,
			1.0f, 0.0f, 0.0f, 0.0f,
			0.0f, 0.0f, 1.0f, 0.0f,
			0.0f, 0.0f, 0.0f, 1.0f
			);
		break;

	default:
		throw ref new Platform::FailureException();
	}

	DX::Try(
		SwapChain->SetRotation(rotation)
		);

	// Create a render target view of the swap chain back buffer.
	ComPtr<ID3D11Texture2D> backBuffer;
	DX::Try(
		SwapChain->GetBuffer(
		0,
		__uuidof(ID3D11Texture2D),
		&backBuffer
		)
		);

	DX::Try(
		D3dDevice->CreateRenderTargetView(
		backBuffer.Get(),
		nullptr,
		&RenderTargetView
		)
		);

	// Create a depth stencil view.
	CD3D11_TEXTURE2D_DESC depthStencilDesc(
		DXGI_FORMAT_D24_UNORM_S8_UINT, 
		static_cast<UINT>(RenderTargetSize.Width),
		static_cast<UINT>(RenderTargetSize.Height),
		1,
		1,
		D3D11_BIND_DEPTH_STENCIL
		);

	ComPtr<ID3D11Texture2D> depthStencil;
	DX::Try(
		D3dDevice->CreateTexture2D(
		&depthStencilDesc,
		nullptr,
		&depthStencil
		)
		);

	CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);
	DX::Try(
		D3dDevice->CreateDepthStencilView(
		depthStencil.Get(),
		&depthStencilViewDesc,
		&DepthStencilView
		)
		);

	// Set the rendering viewport to target the entire window.
	CD3D11_VIEWPORT viewport(
		0.0f,
		0.0f,
		RenderTargetSize.Width,
		RenderTargetSize.Height
		);

	D3dContext->RSSetViewports(1, &viewport);
}

void SenzibleGame::Present()
{
	// The application may optionally specify "dirty" or "scroll"
	// rects to improve efficiency in certain scenarios.
	DXGI_PRESENT_PARAMETERS parameters = {0};
	parameters.DirtyRectsCount = 0;
	parameters.pDirtyRects = nullptr;
	parameters.pScrollRect = nullptr;
	parameters.pScrollOffset = nullptr;

	// The first argument instructs DXGI to block until VSync, putting the application
	// to sleep until the next VSync. This ensures we don't waste any cycles rendering
	// frames that will never be displayed to the screen.
	HRESULT hr = SwapChain->Present1(1, 0, &parameters);

	// Discard the contents of the render target.
	// This is a valid operation only when the existing contents will be entirely
	// overwritten. If dirty or scroll rects are used, this call should be removed.
	D3dContext->DiscardView(RenderTargetView.Get());

	// Discard the contents of the depth stencil.
	D3dContext->DiscardView(DepthStencilView.Get());

	// If the device was removed either by a disconnect or a driver upgrade, we 
	// must recreate all device resources.
	if (hr == DXGI_ERROR_DEVICE_REMOVED)
	{
		HandleDeviceLost();
	}
	else
	{
		DX::Try(hr);
	}
}

float SenzibleGame::ConvertDipsToPixels(float dips)
{
	static const float dipsPerInch = 96.0f;
	return floor(dips * DisplayProperties::LogicalDpi / dipsPerInch + 0.5f); // Round to nearest integer.
}

// Recreate all device resources and set them back to the current state.
void SenzibleGame::HandleDeviceLost()
{
	// Reset these member variables to ensure that UpdateForWindowSizeChange recreates all resources.
	/*m_windowBounds.Width = 0;
	m_windowBounds.Height = 0;
	m_swapChain = nullptr;*/
	/*
	CreateDeviceResources();
	UpdateForWindowSizeChange();*/
}